/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	GeometryGenerator.cpp
*
*	Comments	-	See GeometryGenerator.h
*
**************************************************************************************/
#include "../Include/GeometryGenerator.h"
#include "../Include/Geometry.h"
#include "../Include/GraphicsResourceManager.h"

namespace Pulse
{
	// Internal structs used
	/////////////////////////////
	struct Vertex
	{
		Vertex(){}
		Vertex(const Vector3& p, const Vector3& n, const Vector3& t, const Vector2& uv)
			: position(p), normal(n), tangentU(t), texC(uv){}
		Vertex(
			float px, float py, float pz, 
			float nx, float ny, float nz,
			float tx, float ty, float tz,
			float u, float v)
			: position(px,py,pz), normal(nx,ny,nz),
			tangentU(tx, ty, tz), texC(u,v){}

		Vector3 position;
		Vector3 normal;
		Vector3 tangentU;
		Vector2 texC;
	};

	struct MeshData
	{
		typedef Array<Vertex> VertexList;
		typedef Array<UINT> IndexList;

		VertexList	vertices;
		IndexList	indices;
	};

	// GeometryGenerator
	/////////////////////////
	Geometry * GeometryGenerator::CreateSphere( FLOAT radius, UINT sliceCount, UINT stackCount, const CHAR *pName )
	{
		MeshData tempMesh;		// Store temporary sphere here
		GeometryDesc geoDesc;	// Geometry description
		Geometry *pGeo;			// Final container for the sphere to be stored

		// Code by Frank Luna.
		//
		// Compute the vertices stating at the top pole and moving down the stacks.
		//

		// Poles: note that there will be texture coordinate distortion as there is
		// not a unique point on the texture map to assign to the pole when mapping
		// a rectangular texture onto a sphere.
		Vertex topVertex(0.0f, +radius, 0.0f, 0.0f, +1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
		Vertex bottomVertex(0.0f, -radius, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);

		tempMesh.vertices.PushBack( topVertex );

		float phiStep   = PSX_PI/stackCount;
		float thetaStep = 2.0f*PSX_PI/sliceCount;

		// Compute vertices for each stack ring (do not count the poles as rings).
		for(UINT i = 1; i <= stackCount-1; ++i)
		{
			float phi = i*phiStep;

			// Vertices of ring.
			for(UINT j = 0; j <= sliceCount; ++j)
			{
				float theta = j*thetaStep;

				Vertex v;

				// spherical to cartesian
				v.position.x = radius*sinf(phi)*cosf(theta);
				v.position.y = radius*cosf(phi);
				v.position.z = radius*sinf(phi)*sinf(theta);

				// Partial derivative of P with respect to theta
				v.tangentU.x = -radius*sinf(phi)*sinf(theta);
				v.tangentU.y = 0.0f;
				v.tangentU.z = +radius*sinf(phi)*cosf(theta);

				v.tangentU.Normalize();

				v.normal = v.position;
				v.normal.Normalize();

				v.texC.x = theta / PSX_2PI; /*XM_2PI*/
				v.texC.y = phi / PSX_PI;

				tempMesh.vertices.PushBack( v );
			}
		}

		tempMesh.vertices.PushBack( bottomVertex );

		//
		// Compute indices for top stack.  The top stack was written first to the vertex buffer
		// and connects the top pole to the first ring.
		//

		for(UINT i = 1; i <= sliceCount; ++i)
		{
			tempMesh.indices.PushBack(0);
			tempMesh.indices.PushBack(i+1);
			tempMesh.indices.PushBack(i);
		}

		//
		// Compute indices for inner stacks (not connected to poles).
		//

		// Offset the indices to the index of the first vertex in the first ring.
		// This is just skipping the top pole vertex.
		UINT baseIndex = 1;
		UINT ringVertexCount = sliceCount+1;
		for(UINT i = 0; i < stackCount-2; ++i)
		{
			for(UINT j = 0; j < sliceCount; ++j)
			{
				tempMesh.indices.PushBack(baseIndex + i*ringVertexCount + j);
				tempMesh.indices.PushBack(baseIndex + i*ringVertexCount + j+1);
				tempMesh.indices.PushBack(baseIndex + (i+1)*ringVertexCount + j);

				tempMesh.indices.PushBack(baseIndex + (i+1)*ringVertexCount + j);
				tempMesh.indices.PushBack(baseIndex + i*ringVertexCount + j+1);
				tempMesh.indices.PushBack(baseIndex + (i+1)*ringVertexCount + j+1);
			}
		}

		//
		// Compute indices for bottom stack.  The bottom stack was written last to the vertex buffer
		// and connects the bottom pole to the bottom ring.
		//

		// South pole vertex was added last.
		UINT southPoleIndex = (UINT)tempMesh.vertices.GetSize()-1;

		// Offset the indices to the index of the first vertex in the last ring.
		baseIndex = southPoleIndex - ringVertexCount;

		for(UINT i = 0; i < sliceCount; ++i)
		{
			tempMesh.indices.PushBack(southPoleIndex);
			tempMesh.indices.PushBack(baseIndex+i);
			tempMesh.indices.PushBack(baseIndex+i+1);
		}

		// Now set up Geometry to hold the tempMesh data

		// Add vertex type
		geoDesc.AddVertexSourceType( EVertexElement::POSITION, (SIZE_T32)tempMesh.vertices.GetSize() );
		geoDesc.AddVertexSourceType( EVertexElement::NORMAL, (SIZE_T32)tempMesh.vertices.GetSize() );
		geoDesc.AddVertexSourceType( EVertexElement::TANGENT, (SIZE_T32)tempMesh.vertices.GetSize() );
		geoDesc.AddVertexSourceType( EVertexElement::TEXTURE_DIFFUSE, (SIZE_T32)tempMesh.vertices.GetSize() );
		
		// Add index element
		geoDesc.AddIndexSource( (SIZE_T32)tempMesh.indices.GetSize() );

		// Transfer tempMesh data to geoDesc
		const SIZE_T numVertices = tempMesh.vertices.GetSize();
		Vector3 *pVSPos = geoDesc.GetVertexSource( EVertexElement::POSITION )->GetAsPositions();
		Vector3 *pVSNormal = geoDesc.GetVertexSource( EVertexElement::NORMAL )->GetAsNormals();
		Vector3 *pVSTangent = geoDesc.GetVertexSource( EVertexElement::TANGENT )->GetAsTangents();
		Vector2 *pVSTexDiffuse = geoDesc.GetVertexSource( EVertexElement::TEXTURE_DIFFUSE )->GetAsTextureDiffuse();

		for ( SIZE_T i = 0; i < numVertices; ++i )
		{
			Vertex *pCurrVertex = &tempMesh.vertices[i];

			pVSPos[i] = pCurrVertex->position;
			pVSNormal[i] = pCurrVertex->normal;
			pVSTangent[i] = pCurrVertex->tangentU;
			pVSTexDiffuse[i] = pCurrVertex->texC;
		}

		const SIZE_T numIndices = tempMesh.indices.GetSize();
		UINT16 *pIndices = geoDesc.GetIndexSource()->GetAs16BitIndices();

		for ( SIZE_T i = 0; i < numIndices; ++i )
		{
			pIndices[i] = tempMesh.indices[i];
		}

		// Lastly, geometry subset
		geoDesc.AddGeometrySubset( (SIZE_T32)tempMesh.indices.GetSize() / 3 ); 

		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();

		if ( pName )
			pGeo = pGM->CreateGeometry( pName, &geoDesc );
		else
			pGeo = pGM->CreateGeometry( &geoDesc );

		return pGeo;
	}

	Geometry * GeometryGenerator::CreateFullscreenQuad( const CHAR *pName )
	{
		GeometryDesc geoDesc;

		geoDesc.AddVertexSourceType(EVertexElement::POSITION, 4 );
		geoDesc.AddVertexSourceType(EVertexElement::TEXTURE_DIFFUSE, 4 );
		geoDesc.AddIndexSource( 6 );

		Vector3 *pPos = geoDesc.GetVertexSource( EVertexElement::POSITION )->GetAsPositions();
		Vector2 *pNormals = geoDesc.GetVertexSource( EVertexElement::TEXTURE_DIFFUSE )->GetAsTextureDiffuse();
		UINT16 *pIndices = geoDesc.GetIndexSource()->GetAs16BitIndices();

		// Set vertex pos
		pPos[0] = Vector3( -1.0f, -1.0f, 0.0f );	pNormals[0] = Vector2( 0.0f, 1.0f );
		pPos[1] = Vector3( -1.0f, 1.0f, 0.0f );		pNormals[1] = Vector2( 0.0f, 0.0f );
		pPos[2] = Vector3( 1.0f, 1.0f, 0.0f );		pNormals[2] = Vector2( 1.0f, 0.0f );
		pPos[3] = Vector3( 1.0f, -1.0f, 0.0f );		pNormals[3] = Vector2( 1.0f, 1.0f );

		//Set indices
		pIndices[0] = 0;
		pIndices[1] = 1;
		pIndices[2] = 2;

		pIndices[3] = 0;
		pIndices[4] = 2;
		pIndices[5] = 3;

		// geo subset
		geoDesc.AddGeometrySubset( 2 );

		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		Geometry *pGeo;

		if ( pName )
			pGeo = pGM->CreateGeometry( pName, &geoDesc );
		else
			pGeo = pGM->CreateGeometry( &geoDesc );

		return pGeo;

	}

}